﻿#region Copyright
// Copyright (c) 2011-2012 RaisingForce Team (Alikin Sergey)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Diagnostics.Contracts;
using System.Threading;
using Aiemp.ObjectModel;
using RaisingForce.Raise.ObjectModel;
using System.Runtime.Serialization;
using System.Xml;

namespace RaisingForce.Raise.Measures
{
    [ContractClass(typeof(PositiveDoubleMeasureProviderContract))]
    internal abstract class PositiveDoubleMeasureProvider<TMeasure, TProvider> :
        Singleton<TProvider>
        where TMeasure : class, IMeasure<double>
        where TProvider : PositiveDoubleMeasureProvider<TMeasure, TProvider>
    {
        #region Constructors

        protected PositiveDoubleMeasureProvider()
            : base()
        { }

        #endregion

        #region Private Declarations

        private bool invariantsAreEqual(
            double firstInvariantValue,
            double secondInvariantValue)
        {
            double deltaInvariantValue = Math.Abs(firstInvariantValue - secondInvariantValue);
            return deltaInvariantValue < this.EpsilonInvariantValue;
        }

        private T getBinaryFuncResult<T>(
            DoubleMeasureValue<TMeasure> first,
            DoubleMeasureValue<TMeasure> second,
            Func<double, double, T> operatorFunc,
            T bothAreUnknownValues,
            T firstIsUnknownValue,
            T secondIsUnknownValue)
        {
            Contract.Requires(!first.IsDefault());
            Contract.Requires(!second.IsDefault());
            Contract.Requires(operatorFunc != null);
            bool firstIsUnknown = this.IsUnknown(first);
            bool secondIsUnknown = this.IsUnknown(second);
            if (firstIsUnknown && secondIsUnknown)
            {
                return bothAreUnknownValues;
            }
            if (firstIsUnknown)
            {
                return firstIsUnknownValue;
            }
            if (secondIsUnknown)
            {
                return secondIsUnknownValue;
            }
            double firstInvariant = first.GetInvariantValue();
            double secondInvariant = second.GetInvariantValue();
            return operatorFunc(firstInvariant, secondInvariant);
        }

        private DoubleMeasureValue<TMeasure> getBinaryFuncResult(
            DoubleMeasureValue<TMeasure> first,
            DoubleMeasureValue<TMeasure> second,
            Func<double, double, double> operatorFunc)
        {
            Contract.Requires(!first.IsDefault());
            Contract.Requires(!second.IsDefault());
            Contract.Requires(operatorFunc != null);
            Contract.Ensures(!Contract.Result<DoubleMeasureValue<TMeasure>>().IsDefault());
            bool firstIsUnknown = this.IsUnknown(first);
            bool secondIsUnknown = this.IsUnknown(second);
            if (firstIsUnknown || secondIsUnknown)
            {
                return this.Unknown;
            }
            double firstInvariant = first.GetInvariantValue();
            double secondInvariant = second.GetInvariantValue();
            double resultInvariant = operatorFunc(firstInvariant, secondInvariant);
            TMeasure firstMeasure = first.InnerMeasure;
            double resultInFirstMeasure = firstMeasure.ToMeasure(resultInvariant);
            return new DoubleMeasureValue<TMeasure>(resultInFirstMeasure, firstMeasure);
        }

        private DoubleMeasureValue<TMeasure> getBinaryFuncResult(
            DoubleMeasureValue<TMeasure> first,
            double secondInvariant,
            Func<double, double, double> operatorFunc)
        {
            Contract.Requires(!first.IsDefault());
            Contract.Ensures(!Contract.Result<DoubleMeasureValue<TMeasure>>().IsDefault());
            bool firstIsUnknown = this.IsUnknown(first);
            bool secondIsUnknown = this.invariantsAreEqual(secondInvariant, UnknownValue);
            if (firstIsUnknown || secondIsUnknown)
            {
                return this.Unknown;
            }
            double firstInvariant = first.GetInvariantValue();
            double resultInvariant = operatorFunc(firstInvariant, secondInvariant);
            TMeasure firstMeasure = first.InnerMeasure;
            double resultInFirstMeasure = firstMeasure.ToMeasure(resultInvariant);
            return new DoubleMeasureValue<TMeasure>(resultInFirstMeasure, firstMeasure);
        }

        #endregion

        #region Protected Declarations

        [Pure]
        protected string GetValueString(
            double value,
            string format,
            IFormatProvider formatProvider)
        {
            Contract.Requires(this.ValueIsValid(value));
            Contract.Ensures(Contract.Result<string>() != null);
            IFormatProvider usingFormatProvider =
                formatProvider ?? Thread.CurrentThread.CurrentCulture;
            return value.ToString(format, usingFormatProvider);
        }

        #endregion

        #region Constants

        public const double UnknownValue = 0.0d;

        #endregion

        #region Properties

        public DoubleMeasureValue<TMeasure> Unknown
        {
            get
            {
                Contract.Ensures(!Contract.Result<DoubleMeasureValue<IMeasure<double>>>().IsDefault());
                return new DoubleMeasureValue<TMeasure>(
                    PositiveDoubleMeasureProvider<TMeasure, TProvider>.UnknownValue,
                    this.InvariantMeasure);
            }
        }

        public DoubleMeasureValue<TMeasure> Epsilon
        {
            get
            {
                return new DoubleMeasureValue<TMeasure>(
                    this.EpsilonInvariantValue,
                    this.InvariantMeasure);
            }
        }

        #endregion

        #region Protected Abstract And Virtual Members

        protected abstract TMeasure DefaultMeasure { get; }

        protected abstract TMeasure InvariantMeasure { get; }

        [Pure]
        protected abstract string GetString(
            DoubleMeasureValue<TMeasure> value,
            string format,
            IFormatProvider formatProvider);

        protected abstract double EpsilonInvariantValue { get; }

        public virtual string UnknownString
        {
            get
            {
                Contract.Ensures(Contract.Result<string>() != null);
                return "unknown";
            }
        }

        #endregion

        #region Public Methods

        [Pure]
        public bool IsUnknown(DoubleMeasureValue<TMeasure> value)
        {
            Contract.Requires(!value.IsDefault());
            return this.invariantsAreEqual(value.InnerValue, UnknownValue);
        }

        [Pure]
        public double Get(
            DoubleMeasureValue<TMeasure> value,
            TMeasure measure)
        {
            Contract.Requires(!value.IsDefault());
            if (measure == value.InnerMeasure)
            {
                return value.InnerValue;
            }
            else
            {
                double invariantValue = value.GetInvariantValue();
                return measure.ToMeasure(invariantValue);
            }
        }

        [Pure]
        public bool Equals(
            DoubleMeasureValue<TMeasure> value,
            DoubleMeasureValue<TMeasure> other)
        {
            Contract.Requires(!value.IsDefault());
            Contract.Requires(!other.IsDefault());
            return this.getBinaryFuncResult(
                value,
                other,
                (x, y) =>
                    this.invariantsAreEqual(x, y) ?
                    true :
                    x.Equals(y),
                    bothAreUnknownValues: true,
                    firstIsUnknownValue: false,
                    secondIsUnknownValue: false);
        }

        [Pure]
        public int CompareTo(
            DoubleMeasureValue<TMeasure> value,
            DoubleMeasureValue<TMeasure> other)
        {
            Contract.Requires(!value.IsDefault());
            Contract.Requires(!other.IsDefault());
            return this.getBinaryFuncResult(
                value,
                other,
                (x, y) =>
                    this.invariantsAreEqual(x, y) ?
                    0 :
                    x.CompareTo(y),
                    bothAreUnknownValues: 0,
                    firstIsUnknownValue: -1,
                    secondIsUnknownValue: 1);
        }

        [Pure]
        public int GetHashCode(DoubleMeasureValue<TMeasure> value)
        {
            Contract.Requires(!value.IsDefault());
            double valueInDefaultMeasure =
                this.Get(value, this.DefaultMeasure);
            return valueInDefaultMeasure.GetHashCode();
        }

        [Pure]
        public string ToString(
            DoubleMeasureValue<TMeasure> value,
            string format = null,
            IFormatProvider formatProvider = null)
        {
            Contract.Requires(!value.IsDefault());
            if (this.IsUnknown(value))
            {
                return this.UnknownString;
            }
            return this.GetString(value, format, formatProvider);
        }

        [Pure]
        public DoubleMeasureValue<TMeasure> GetDefault(
            DoubleMeasureValue<TMeasure> value)
        {
            Contract.Requires(!value.IsDefault());
            double innerValue = this.Get(value, this.DefaultMeasure);
            return new DoubleMeasureValue<TMeasure>(innerValue, this.DefaultMeasure);
        }

        [Pure]
        public DoubleMeasureValue<TMeasure> InterpretAsDefault(
            DoubleMeasureValue<TMeasure> value)
        {
            // value.IsDefault() может выполняться
            return new DoubleMeasureValue<TMeasure>(
                value.InnerValue,
                this.DefaultMeasure);
        }

        [Pure]
        public bool ValueIsValid(double measureValue)
        {
            return measureValue > 0
                || measureValue.Equals(UnknownValue);
        }

        #endregion

        #region Public Serialization Members

        public const string ValueSerializationName = "value";

        public void GetSerializationData(
            SerializationInfo info,
            DoubleMeasureValue<TMeasure> value)
        {
            Contract.Requires(info != null);
            //Contract.Requires(value.InnerMeasure == this.DefaultMeasure);
            info.AddValue(ValueSerializationName, value.InnerValue);
        }

        public DoubleMeasureValue<TMeasure> SerializationConstructor(
            SerializationInfo info)
        {
            Contract.Requires(info != null);
            double innerValue = info.GetDouble(ValueSerializationName);
            return new DoubleMeasureValue<TMeasure>(innerValue, this.DefaultMeasure);
        }

        public DoubleMeasureValue<TMeasure> ReadXml(XmlReader reader)
        {
            Contract.Requires(reader != null);
            double innerValue = reader.ReadElementContentAsDouble();
            return new DoubleMeasureValue<TMeasure>(innerValue, this.DefaultMeasure);
        }

        public void WriteXml(XmlWriter writer, DoubleMeasureValue<TMeasure> value)
        {
            Contract.Requires(writer != null);
            //Contract.Requires(value.InnerMeasure == this.DefaultMeasure);
            writer.WriteValue(value.InnerValue);
        }

        #endregion

        #region Public Operator Methods

        [Pure]
        public bool OpEquality(
            DoubleMeasureValue<TMeasure> first,
            DoubleMeasureValue<TMeasure> second)
        {
            Contract.Requires(!first.IsDefault());
            Contract.Requires(!second.IsDefault());
            return this.getBinaryFuncResult(
                first,
                second,
                (x, y) =>
                    this.invariantsAreEqual(x, y) ?
                    true :
                    x == y,
                    bothAreUnknownValues: true,
                    firstIsUnknownValue: false,
                    secondIsUnknownValue: false);
        }

        [Pure]
        public bool OpInequality(
            DoubleMeasureValue<TMeasure> first,
            DoubleMeasureValue<TMeasure> second)
        {
            Contract.Requires(!first.IsDefault());
            Contract.Requires(!second.IsDefault());
            return this.getBinaryFuncResult(
                first,
                second,
                (x, y) =>
                    this.invariantsAreEqual(x, y) ?
                    false :
                    x != y,
                    bothAreUnknownValues: false,
                    firstIsUnknownValue: true,
                    secondIsUnknownValue: true);
        }

        [Pure]
        public bool OpLessThan(
            DoubleMeasureValue<TMeasure> first,
            DoubleMeasureValue<TMeasure> second)
        {
            Contract.Requires(!first.IsDefault());
            Contract.Requires(!second.IsDefault());
            return this.getBinaryFuncResult(
                first,
                second,
                (x, y) =>
                    this.invariantsAreEqual(x, y) ?
                    false :
                    x < y,
                    bothAreUnknownValues: false,
                    firstIsUnknownValue: true,
                    secondIsUnknownValue: false);
        }

        [Pure]
        public bool OpLessThanOrEqual(
            DoubleMeasureValue<TMeasure> first,
            DoubleMeasureValue<TMeasure> second)
        {
            Contract.Requires(!first.IsDefault());
            Contract.Requires(!second.IsDefault());
            return this.getBinaryFuncResult(
                first,
                second,
                (x, y) =>
                    this.invariantsAreEqual(x, y) ?
                    true :
                    x <= y,
                    bothAreUnknownValues: true,
                    firstIsUnknownValue: true,
                    secondIsUnknownValue: false);
        }

        [Pure]
        public bool OpGreaterThan(
            DoubleMeasureValue<TMeasure> first,
            DoubleMeasureValue<TMeasure> second)
        {
            Contract.Requires(!first.IsDefault());
            Contract.Requires(!second.IsDefault());
            return this.getBinaryFuncResult(
                first,
                second,
                (x, y) =>
                    this.invariantsAreEqual(x, y) ?
                    false :
                    x > y,
                    bothAreUnknownValues: false,
                    firstIsUnknownValue: false,
                    secondIsUnknownValue: true);
        }

        [Pure]
        public bool OpGreaterThanOrEqual(
            DoubleMeasureValue<TMeasure> first,
            DoubleMeasureValue<TMeasure> second)
        {
            Contract.Requires(!first.IsDefault());
            Contract.Requires(!second.IsDefault());
            return this.getBinaryFuncResult(
                first,
                second,
                (x, y) =>
                    this.invariantsAreEqual(x, y) ?
                    true :
                    x >= y,
                    bothAreUnknownValues: true,
                    firstIsUnknownValue: false,
                    secondIsUnknownValue: true);
        }

        [Pure]
        public DoubleMeasureValue<TMeasure> OpAddition(
            DoubleMeasureValue<TMeasure> first,
            DoubleMeasureValue<TMeasure> second)
        {
            Contract.Requires(!first.IsDefault());
            Contract.Requires(!second.IsDefault());
            Contract.Ensures(!Contract.Result<DoubleMeasureValue<TMeasure>>().IsDefault());
            return this.getBinaryFuncResult(first, second, (x, y) => x + y);
        }

        [Pure]
        public DoubleMeasureValue<TMeasure> OpSubtraction(
            DoubleMeasureValue<TMeasure> first,
            DoubleMeasureValue<TMeasure> second)
        {
            Contract.Requires(!first.IsDefault());
            Contract.Requires(!second.IsDefault());
            Contract.Ensures(!Contract.Result<DoubleMeasureValue<TMeasure>>().IsDefault());
            return this.getBinaryFuncResult(first, second, (x, y) => Math.Abs(x - y));
        }

        [Pure]
        public DoubleMeasureValue<TMeasure> OpMultiply(
            DoubleMeasureValue<TMeasure> first,
            DoubleMeasureValue<TMeasure> second)
        {
            Contract.Requires(!first.IsDefault());
            Contract.Requires(!second.IsDefault());
            Contract.Ensures(!Contract.Result<DoubleMeasureValue<TMeasure>>().IsDefault());
            return this.getBinaryFuncResult(first, second, (x, y) => x * y);
        }

        [Pure]
        public DoubleMeasureValue<TMeasure> OpDivision(
            DoubleMeasureValue<TMeasure> first,
            DoubleMeasureValue<TMeasure> second)
        {
            Contract.Requires(!first.IsDefault());
            Contract.Requires(!second.IsDefault());
            Contract.Ensures(!Contract.Result<DoubleMeasureValue<TMeasure>>().IsDefault());
            return this.getBinaryFuncResult(first, second, (x, y) => x / y);
        }

        [Pure]
        public DoubleMeasureValue<TMeasure> OpMultiply(
            DoubleMeasureValue<TMeasure> first,
            double secondInvariant)
        {
            Contract.Requires(!first.IsDefault());
            Contract.Requires(ValueIsValid(secondInvariant));
            Contract.Ensures(!Contract.Result<DoubleMeasureValue<TMeasure>>().IsDefault());
            return this.getBinaryFuncResult(first, secondInvariant, (x, y) => x * y);
        }

        [Pure]
        public DoubleMeasureValue<TMeasure> OpDivision(
            DoubleMeasureValue<TMeasure> first,
            double secondInvariant)
        {
            Contract.Requires(!first.IsDefault());
            Contract.Requires(ValueIsValid(secondInvariant));
            Contract.Ensures(!Contract.Result<DoubleMeasureValue<TMeasure>>().IsDefault());
            return this.getBinaryFuncResult(first, secondInvariant, (x, y) => x / y);
        }

        #endregion
    }
}
